Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ


Displaying Raw β€’ View rendered β€’ Download

feature/widget/README.md 4d4070c8e1ed73a56ee2f054300a0f62e3fe2e62 (4d4070c8) Text, 6.13 KB

Tc9d1d9# `:feature:widget`

Tc9d1d9## Overview

The Ta5d6ff`:feature:widget` module is an **Android-only Jetpack Glance home-screen widget** (API 26+). It exposes a single widget β€” Ta5d6ff`LocalStatsWidget` β€” that displays live mesh statistics sourced from the running Meshtastic service.

**Target: Android only** (not KMP β€” uses Ta5d6ff`meshtastic.android.library` + Ta5d6ff`meshtastic.android.library.compose` convention plugins)

Tc9d1d9## Key Responsibilities

Tff7b72- Display live mesh statistics on the Android home screen via Jetpack Glance
Tff7b72- Combine four reactive data sources into a single Ta5d6ff`StateFlow<LocalStatsWidgetUiState>` for efficient re-renders
Tff7b72- Support three responsive widget sizes: small square, wide rectangle, and large square
Tff7b72- Fulfil the Ta5d6ff`AppWidgetUpdater` interface from Ta5d6ff`:core:repository` so the mesh service can trigger widget refreshes without depending on Android widget APIs

Tc9d1d9## Source Structure

Ta5d6ff```
src/main/kotlin/org/meshtastic/feature/widget/
β”œβ”€β”€ LocalStatsWidget.kt ← GlanceAppWidget, 3 responsive sizes
β”œβ”€β”€ LocalStatsWidgetReceiver.kt ← GlanceAppWidgetReceiver
β”œβ”€β”€ LocalStatsWidgetState.kt ← LocalStatsWidgetUiState + StateProvider
β”œβ”€β”€ AndroidAppWidgetUpdater.kt ← implements AppWidgetUpdater (core:repository)
β”œβ”€β”€ RefreshLocalStatsAction.kt ← GlanceActionCallback (refresh button)
└── di/
└── FeatureWidgetModule.kt
```

Tc9d1d9## Widget Content

The widget shows the following information when connected:

| Section | Data shown |
|---|---|
| Node identity | Short name chip with node colours |
| Battery | Level percentage + progress bar |
| Channel utilization | Percentage + progress bar |
| Air utilization | Percentage + progress bar |
| Traffic | TX / RX packet counts, duplicates, relay TX, relay cancelled, dropped, bad RX |
| Diagnostics | Noise floor (dBm), free heap / total heap |
| Footer | Online nodes / total nodes, device uptime, last-updated timestamp |

When disconnected, the widget shows a "Not Connected" state with the last-known node name.

Tc9d1d9## Key Types

Tc9d1d9### `LocalStatsWidgetUiState`

Ta5d6ff```Ta5d6ffkotlin
Tff7b72data Tff7b72class T56d364LocalStatsWidgetUiStateTb4b4b4(
Tff7b72val Te6edf3connectionStateTb4b4b4: Te6edf3ConnectionStateTb4b4b4,
Tff7b72val Te6edf3isConnectingTb4b4b4: Tffa657BooleanTb4b4b4,
Tff7b72val Te6edf3showContentTb4b4b4: Tffa657BooleanTb4b4b4,
T8b949e// Node identity
Tff7b72val Te6edf3nodeShortNameTb4b4b4: Tffa657String?Tb4b4b4,
Tff7b72val Te6edf3nodeColorsTb4b4b4: Te6edf3PairTff7b72<Tffa657IntTb4b4b4, Tffa657IntTff7b72>Tff7b72?Tb4b4b4,
T8b949e// Battery
Tff7b72val Te6edf3batteryLevelTb4b4b4: Tffa657Int?Tb4b4b4,
Tff7b72val Te6edf3hasBatteryTb4b4b4: Tffa657BooleanTb4b4b4,
T8b949e// Utilization
Tff7b72val Te6edf3channelUtilizationTb4b4b4: Tffa657FloatTb4b4b4,
Tff7b72val Te6edf3airUtilizationTb4b4b4: Tffa657FloatTb4b4b4,
T8b949e// Traffic counters
Tff7b72val Te6edf3numPacketsTxTb4b4b4: Tffa657IntTb4b4b4, Tff7b72val Te6edf3numPacketsRxTb4b4b4: Tffa657IntTb4b4b4,
Tff7b72val Te6edf3numRxDupeTb4b4b4: Tffa657IntTb4b4b4, Tff7b72val Te6edf3numTxRelayTb4b4b4: Tffa657IntTb4b4b4, Tff7b72val Te6edf3numTxRelayCanceledTb4b4b4: Tffa657IntTb4b4b4,
Tff7b72val Te6edf3numTxDroppedTb4b4b4: Tffa657IntTb4b4b4, Tff7b72val Te6edf3numPacketsRxBadTb4b4b4: Tffa657IntTb4b4b4,
T8b949e// Diagnostics
Tff7b72val Te6edf3noiseFloorTb4b4b4: Tffa657Int?Tb4b4b4,
Tff7b72val Te6edf3heapFreeBytesTb4b4b4: Tffa657LongTb4b4b4, Tff7b72val Te6edf3heapTotalBytesTb4b4b4: Tffa657LongTb4b4b4,
T8b949e// Footer
Tff7b72val Te6edf3totalNodesTb4b4b4: Tffa657IntTb4b4b4, Tff7b72val Te6edf3onlineNodesTb4b4b4: Tffa657IntTb4b4b4,
Tff7b72val Te6edf3uptimeSecsTb4b4b4: Tffa657Int?Tb4b4b4,
Tff7b72val Te6edf3updateTimeMillisTb4b4b4: Tffa657LongTb4b4b4,
Tb4b4b4)
Ta5d6ff```

Tc9d1d9### `LocalStatsWidgetStateProvider`

Koin Ta5d6ff`@Single` that eagerly combines four reactive flows (Ta5d6ff`ConnectionStateProvider` is injected as the constructor param Ta5d6ff`connectionStateProvider`):
Tff7b72- Ta5d6ff`ConnectionStateProvider.connectionState`
Tff7b72- Ta5d6ff`NodeRepository.nodeDBbyNum` (online/total counts)
Tff7b72- Ta5d6ff`NodeRepository.localStats`
Tff7b72- Ta5d6ff`NodeRepository.ourNodeInfo`

Ta5d6ff```Ta5d6ffkotlin
Tff7b72val Te6edf3stateTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3LocalStatsWidgetUiStateTff7b72>
Ta5d6ff```

Tc9d1d9### `LocalStatsWidget`

Ta5d6ff```Ta5d6ffkotlin
Tff7b72class T56d364LocalStatsWidget Tb4b4b4: Te6edf3GlanceAppWidgetTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3KoinComponent Tb4b4b4{
Tff7b72override Tff7b72val Te6edf3sizeMode Tff7b72= Te6edf3SizeModeTb4b4b4.Te6edf3ResponsiveTb4b4b4(
Te6edf3setOfTb4b4b4(Te6edf3DpSizeTb4b4b4(T79c0ff1T79c0ff0T79c0ff0.Te6edf3dpTb4b4b4, T79c0ff1T79c0ff0T79c0ff0.Te6edf3dpTb4b4b4)Tb4b4b4, Te6edf3DpSizeTb4b4b4(T79c0ff2T79c0ff5T79c0ff0.Te6edf3dpTb4b4b4, T79c0ff1T79c0ff0T79c0ff0.Te6edf3dpTb4b4b4)Tb4b4b4, Te6edf3DpSizeTb4b4b4(T79c0ff2T79c0ff5T79c0ff0.Te6edf3dpTb4b4b4, T79c0ff2T79c0ff5T79c0ff0.Te6edf3dpTb4b4b4)Tb4b4b4)
Tb4b4b4)
Tb4b4b4}
Ta5d6ff```

Tapping the widget opens the app. The refresh button fires Ta5d6ff`RefreshLocalStatsAction`, which calls Ta5d6ff`AppWidgetUpdater.updateAll()`.

Tc9d1d9## `AppWidgetUpdater` integration

The mesh service holds a reference to Ta5d6ff`AppWidgetUpdater` (defined in Ta5d6ff`:core:repository`) without depending on Android widget APIs. Ta5d6ff`AndroidAppWidgetUpdater` in this module provides the concrete implementation:

Ta5d6ff```Ta5d6ffkotlin
T8b949e// Simplified β€” the real updateAll() wraps the call in a try/catch
Tf0883e@Single
Tff7b72class T56d364AndroidAppWidgetUpdaterTb4b4b4(
Tff7b72private Tff7b72val Te6edf3contextTb4b4b4: Te6edf3ContextTb4b4b4,
Te6edf3stateProviderTb4b4b4: Te6edf3LocalStatsWidgetStateProviderTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3AppWidgetUpdater Tb4b4b4{
Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffupdateAllTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3LocalStatsWidgetTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3updateAllTb4b4b4(Te6edf3contextTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Ta5d6ff```

It also observes Ta5d6ff`stateProvider.state` itself (debounced 500 ms, ignoring timestamp-only changes) and re-renders whenever widget instances exist β€” Glance compositions are ephemeral, so re-renders must be driven externally. This keeps the service layer platform-agnostic while still enabling widget refresh on data changes.

Tc9d1d9## Dependency Graph

Tc9d1d9### Key Dependencies

Ta5d6ff```Ta5d6fftext
feature:widget (Android only)
β”œβ”€β”€ core:common, core:model, core:resources, core:repository
β”œβ”€β”€ androidx.glance.appwidget, glance.material3, glance.preview, glance.appwidget.preview
└── compose.multiplatform.ui (LocalConfiguration, LocalDensity)
Ta5d6ff```

<!--region graph-->
Ta5d6ff```Ta5d6ffmermaid
Ta5d6ffgraph TB
:feature:widget[widget]:::android-feature
:feature:widget -.-> :core:common
:feature:widget -.-> :core:model
:feature:widget -.-> :core:resources
:feature:widget -.-> :core:repository

classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-application-compose fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef compose-desktop-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-library-compose fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-library-compose fill:#FFC1CC,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-library fill:#FFC1CC,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;

Ta5d6ff```
<!--endregion-->

Served by rngit 1.5.4 - Generated in 0.04s